Chapter 2

Durk Jan de Bruin

BANNERS WITH CLASS

Getting the word out in Egypt: Banners have been popular from ancient times. Imagine the frustration of early banner writers when they made an error. On-line banners are much easier to correct.

From ancient times banners have proclaimed events and advertised opportunities. Computers allow individuals to create personalized banners of varied sizes and styles. This program prints block versions of letters in user-specified sizes. It can be altered to create banners with such messages as PYTHON or NOT or POT.

Problem Statement

Write a Python program that reads letters supplied by the user and prints them down the screen in a block format. The program should first prompt the user for a value—the letter size—that specifies the number of printed lines and columns that will be used to print each letter. After reading the letter size, the program should prompt the user for six letters. Finally, it should read and print the six letters on the terminal screen in a block format.

You may assume that the user will provide a letter size value between 5 and 24, and that he or she will provide only letters. For example, here is how the program might work. The user input is indicated in boldface.

Type a size for the letters between 5 and 24? 9
Type six letters chosen from PYTHON: POT

* * * * * * * * *
* * * * * * * * *
* ** *
* ** *
* * * * * * * * *
* * * * * * * * *
* *
* *
* *

* * * * * * * * *
* * * * * * * * *
* ** *
* ** *
* ** *
* ** *
* ** *
* * * * * * * * *
* * * * * * * * *

(Block version of T is then printed.)

The program should be easy to modify, to add more letters and eventually to print banners.
Assume also that this program is being written as part of a collection of programs intended to display letters on a variety of output devices (terminal screens, printers, or plotters). Thus, instead of printing with print function, the program should make calls to three functions, DrawBar, DrawIndentedBar, and DrawTwoBars, to create the letters. The parameters for these functions include the number of lines—the height—and the number of columns—the width—needed to draw the corresponding figure. Here are descriptions of the functions.

  • DrawBar, given height and width values, prints a bar composed of asterisks of the specified height and width. Its heading is

def DrawBar(height, width):

A sample call,

DrawBar(3, 5);

would produce the following output.

* * * * *
* * * * *
* * * * *

  • DrawIndentedBar, given values for height, width, and the number of columns to indent, prints a bar indented by the specified amount. Its heading is

def DrawIndentedBar(height, width, indentAmt):

A sample call,

DrawIndentedBar(3, 5, 8);

would produce the following output. (Eight blanks precede each line of asterisks.)

* * * * *
* * * * *
* * * * *

  • DrawTwoBars, given values for height, width, and amount of separation, prints two bars of the specified height and width, separated by the specified number of columns. Its heading is

def DrawTwoBars (height, width, separation):

A sample call,

DrawTwoBars(3, 5, 8);

would produce the following output. (Eight blanks separate each sequence of asterisks.)

* * * * ** * * * *
* * * * ** * * * *
* * * * ** * * * *

You may choose how to use these functions to print attractive-looking letters.

  1. 1
  2. 2

Chapter 2

Durk Jan de Bruin

Reflection

2.1 Which of the following is more attractive to you? Briefly explain.

Reflection

2.2 Design block versions of the letters P, Y, T and H for letter size 9. Keep track of the decisions you make.

Analysis

2.3 What other letters can be drawn using the functions Drawbar, DrawIndentedBar, and DrawTwoBars?

Analysis

2.4 Name five letters that cannot be drawn easily using Drawbar, DrawIndentedBar, and DrawTwoBars. What extra functions would be needed to draw these letters?

Application

2.5 Write a sentence composed completely of words made out of the letters P, Y, T, H, O and N.


Preparation

The reader is expected to be familiar with the for statement and the input, print functions. This case study introduces the use of functions and the if statements.


Initial Design Steps

How should one start solving this problem?

As in Check That Number! this solution involves breaking or decomposing the problem into smaller subproblems, figuring out solutions for the smaller problems, and putting the solutions together.

Stop & Predict

What are the main subproblems that will need to be solved?

One set of subproblems focuses on the letters: first figure out how to draw each letter and then combine the solutions for the letters. This does not sound much easier than the original problem.
Another set of subproblems focuses on what has to be done to design each letter: read the size, read the letter, draw the letter, and so on. This sounds a little easier than the original problem.
A good way to approach a solution is to design top-down, from the least detailed part of the program to the most detailed. This would involve starting at the main program, then designing the functions that the main program calls, and so on. The top-down approach is usually preferred because it is easier to keep track of the structure of the program and how its parts fit together.

What are the big steps for the top-down design of the program?

Often a problem statement provides the main steps in the top-down design of the solution.

Stop & Predict

What steps are given in the problem description for Banners With CLASS.

The problem statement for Banners With CLASS lists three steps: ask for and read a number saying how large the letters should be drawn, ask for six letters, and read and draw the letters.

What decomposition of these steps makes sense?

The problem statement suggests three big steps for the decomposition. However, these steps are not really parallel. One deals with the letter size and two deal with the letters. We decide to start with two steps: one dealing with the letter size, the other with the letters themselves. Asking for letters goes with reading and printing them in the same way that asking for the letter size goes with reading it. We outline the high-level decomposition as follows:

block letter program:

ask the user for the letter size, then read it;

ask the user for the letters, and read and draw them.

We will gradually expand the points in the outline to more closely approximate Python code.

What quantities need names?

Assigning names to quantities used in a program as soon as they are identified helps keep them straight and also makes it easier to talk about the problem solution. The best name for a quantity is one that describes it well. Thus, we will represent the letter size in a variable and call it letterSize .

How does the program ask for and read a value for letterSize?

As in Check That Number! we group the two tasks of asking for and reading a value for letterSize. The program should print a prompt like "Enter a size for the letters between 5 and 24." and then use a Python input statement to store the answer typed by the user.

  1. 3
  2. 4

Chapter 2

Durk Jan de Bruin

Stop & Help

Will a letter drawn in 24 lines fit on your terminal screen? What is the size of the largest letter that will fit?

How does the program ask for, read, and draw the letters?

What steps are involved in asking for, reading, and drawing the letters? We continue to describe big steps and to postpone the details.

Stop & Predict

How can the task of drawing the letters be divided into steps just like those selected forgetting the letter size?

This task can be split into the same sort of steps used to ask for and read the letter size. One step "asks for," and the other "reads and draws." An outline of the decomposition so far appears below.

block letter program:

ask the user for the letter size, and read it;

ask for the letter size;

read the letter size;

ask the user for the letters, and read and draw them;

ask the user for the letters;

read and draw them;

How is the task of reading and drawing the letters decomposed?

There are two choices for decomposing the task of reading and drawing the letters. One is to read all six characters and then print the block versions of all six characters. We'll call this the "read all, print all" decomposition. Another decomposition is to read a letter, print it, read another letter, print it, and so on until all the letters are printed. We'll call this the "read one, print one" decomposition. In pseudocode these options look like this:

"Read all, print all:"

read all six letters;

print the block versions of all six letters;

"Read one, print one:"

do the following six times:

read a letter;

print the block versions of the letter;

These decompositions are outlined below.

The "read all, print all" decomposition:

read and draw the letters

read all six letters

do the following six times: read a letter

draw all six letters

do the following for each letter read: draw the letter

The "read one, print one" decomposition:

read and draw the letters

do the following six times:

read a letter;

draw the letter;

What templates do these decompositions use?

The first decomposition employs a template that appeared in Check That Number! and will appear again and again. The "read all, print all" decomposition uses the "input, process, output" template:

input a value or set of values;
process the value(s);
output the value(s).

The "read one, print one" decomposition uses a variation of the "input, process, output" template that inputs, processes, and outputs one item at a time:

read a single value, process it, and output results until there aren't any more values to read.

The "read all, print all" decomposition deals with the input values as a "batch." The "read one, print one" decomposition deals with them item by item.

What other factors affect the choice of decomposition?

Factors that influence the choice of decomposition include the ease of implementing the solution and the chance of making errors. In Check That Number! we chose bulk processing. In the first two solutions to that problem, there was only one piece of information to handle, the integer identification number. In the third solution, four-character variables were used to store the digits of the input number; while this approach simplified the rest of the program, using four separate variables was somewhat clumsy.

The problem of clumsiness arises with the "read all, print all" decomposition here. How will the letters be stored while they are being drawn? We would have to use six-character variables; this is even clumsier than four, and keeping track of six variables would probably be a source of errors.

How does the programming environment affect the choice of decomposition?

A system consideration may affect the choice of decomposition. Some Python programming environments read a character and send it to the program immediately after the character is typed (immediate input), while others wait for the user to type a carriage return before sending any of the characters on the line to the program. If the programming environment uses immediate input, it is possible to get the output mixed up on the screen with the characters being typed by the user.

Stop & Help

Which input method does your system use?

Stop & Predict

For systems with immediate input, which decomposition is best? For systems without immediate input, which decomposition is best? Why?

  1. 5
  2. 6

Chapter 2

Durk Jan de Bruin

Which decomposition will be used in this solution?

To avoid the clumsiness of storing six different character values at once, we will choose the "read one, print one" decomposition. This may cause problems in an environment where immediate input is used, since a block letter may get printed before the remaining letters are input.

What progress has been made on the solution so far?

Progress so far is outlined below.

block letter program:

ask the user for the letter size, and read it;

ask for the letter size;

read the letter size;

ask the user for the letters, and read and draw them;

ask the user for the letters;

read and draw them;

do the following six times:

read a letter;

draw the letter;

Stop & Predict

What Python construct will carry out the ''do six times'" action?

The main program has the following outline:

request letterSize from the user
ask the user for six letters
for letterNum in range(6):
read a letter
draw the letter in block form

We have introduced a variable called letterNum along with a for statement to implement the "do six times" step in the decomposition. In general, a for loop is the easiest way to code the action of "doing something a specified number of times."

What are ways to decompose the "draw the letter" task?

For the "draw the letter" task, we again identify big steps and postpone the details. Recall that the program must be able to draw six letters: P, Y, T, H, O and N.

One step is to figure out which of the six letters the user has entered, so as to produce a block P when the user types P, a block Y when the user types Y, and so on. Thus, the decomposition will have to include a test to determine which letter has been entered.

Another step is to print the letter. One decomposition is letter by letter. In this approach, the letter is treated as a unit. The letter-by-letter approach reflects the purpose of the program which is to print block letters. The pseudocode decomposition for the letter-by-letter approach is

if the letter is P, draw a block P;
if the letter is Y, draw a block Y;
if the letter is T, draw a block T;
if the letter is H, draw a block H;
if the letter is O, draw a block O.
if the letter is N, draw a block N.

Another decomposition is line by line. In this approach the lines are treated as units. The line-by-line approach takes advantage of similarities among the letters. The pseudocode decomposition for the line-by-line approach is

draw the first line or component of the letter, whatever it is;
draw the second line or component of the letter, whatever it is;
draw the last line or component of the letter, whatever it is.

Stop & Predict

Which approach is best: letter by letter or line by line? Why?

Which decomposition makes sense for this problem?

To choose between these alternatives it is useful to consider the purpose of the program, the ways in which it is likely to be modified, the implementation of the solution, and the chance of making errors.

We already mentioned that the letter-by-letter approach is closer to the purpose of the program. It is likely, knowing what we know about the alphabet, that our program will eventually be modified to add the capability of drawing other letters. The problem statement suggested that that will be the case. It will be easier to add the code to draw another letter if the program is already organized around letters rather than in some other way.

Furthermore, a decomposition around lines or components is likely to be complicated. The number of lines used to draw a letter is input by the user and therefore not known in advance. The number of components is known in advance, but it varies greatly among letters. A program organized around components is likely to be harder to keep track of, and therefore it is more likely to contain errors.

Thus, we choose the letter-by-letter decomposition.

How is each letter handled separately?

A Python function will draw each letter: DrawP to draw a P, DrawY to draw an Y, and so on. Each function needs to know how big its letter should be, so letterSize should be passed to each one as a value parameter.

How does the program decide which letter function to call?

There are only five possibilities for letters. Since each possibility is a single character, the if statement is most appropriate for selecting among the letter-drawing functions. A descriptive name for the character variable that holds a letter is letter. This yields the following pseudocode:

if letter == 'P':
DrawP(letterSize)
if letter == 'Y':
DrawY(letterSize)
if letter == 'T':
DrawT(letterSize)
if letter == 'H':
DrawH(letterSize)
if letter == 'O':
DrawO(letterSize)
if letter == 'N':
DrawN(letterSize)

How is the main program coded in Python?

The main program, with some refinement, now looks like this.

letterSize = int(input('Enter a size for the letters between 5 and 24:'))
print('Type six letters, chosen from PYTHON: ')
for letterNum in range(6):
letter = input()
if letter == 'P':
DrawP(letterSize)
if letter == 'Y':
DrawY(letterSize)
if letter == 'T':
DrawT(letterSize)
if letter == 'H':
DrawH(letterSize)
if letter == 'O':
DrawO(letterSize)
if letter == 'N':
DrawN(letterSize)

  1. 7
  2. 8

Chapter 2

Durk Jan de Bruin

Analysis

2.6 In the decompositions considered so far, the task of reading the letters was grouped with the task of drawing the letters rather than with the task of asking for them. Would grouping the actions of prompting for and reading the letters fit better into the "read all, print all" approach or the "read one, print one" approach? Explain.

Reflection

2.7 An outline was used as an aid to keep track of details in the design. What aids have you used to keep track of details in solving a complex problem.

Application

2.8 Design a program that will allow you to determine whether or not the Python environment in which it is run uses immediate input.

Application

2.9 Use a case statement to generate pseudocode for the line-by-line decomposition or for the component-by-component decomposition.


Designing the Letters

What will the letters look like?

The problem statement says that the letters will have the same number of rows (lines) as columns, and that they will have bars, indented bars, and bar pairs. It says to design "attractive" letters.

Stop & Predict

What is a good way to figure out the characteristics of attractive letters?

To figure out what a letter will look like, we select a typical letter and try some options. The problem statement provides the letter A as an example, so we start with it.

The A shown in the problem statement has a top bar, a pair of bars (the "walls" of the A), another bar, and a longer pair of bars (the "legs" of the A). These components of the letter vary in size depending on letterSize. To determine how they vary, we construct these samples:

Stop & Help

Draw the letter S in sizes 5 to 11.

Stop & Help

Figure out the dimensions for the letters S and C. What is the smallest size possible for the letter S? Why?

How are the dimensions for the letter A determined?

We note that the first two components of the letter A are about half of the letter, the bar size is about one quarter of the letter, and the width of each leg is the same as the bar height. These dimensions are shown in the below Table.

Dimensions for various sizes of the letter A

Size of letter Height of both bars Height of walls Width of walls, legs Height of legs
5 1 1 1 2
6 1 2 1 2
7 1 2 1 3
8 2 2 2 2
9 2 2 2 3
10 2 3 2 3
11 2 3 2 4
  1. 9
  2. 10

Chapter 2

Durk Jan de Bruin

How are the dimensions represented in Python?

In Python, we choose a variable for each quantity. Values for the dimensions for the letter A can be computed in terms of letterSize as follows:

topHalfHeight = letterSize / 2
barHeight = letterSize / 4
wallHeight = topHalfHeight - barHeight
wallWidth = barHeight
legHeight = letterSize - topHalfHeight - barHeight
legWidth = barHeight

Stop & Help

Compute the dimensions for each part of the letter S.

How do these dimensions apply to drawing an A?

The problem statement requires that the letters be drawn using the DrawBar, DrawIndentedBar, and DrawTwoBars functions. One way to decompose the drawing of the letter is to draw each line individually with these functions. Loops that draw the components of the A are shown below.


for lineNum in range(0, barHeight):
draw a line with height = 1 and width = letterSize
for lineNum in range(0, wallHeight):
draw two bars with height = 1, width = wallWidth, and letterSize - 2 * wallWidth columns in between
for lineNum in range(0, barHeight):
draw a line with height = 1 and width = letterSize
for lineNum in range(0, legHeight):
draw two bars with height = 1, width = wallWidth, and letterSize - 2 * wallWidth columns in between

Stop & Help

Write loops using code and pseudocode to draw the components of the letter S.

Must loops be used to draw the letter?

Each loop, however, can be rewritten as a single call to DrawBar or DrawTwoBars, since each of the two functions can take an arbitrary height value as an argument. This yields the following code:

DrawBar(barHeight, letterSize)
DrawTwoBars(wallHeight, wallWidth, letterSize - 2 * wallWidth)
DrawBar(barHeight, letterSize)
DrawTwoBars(legHeight, legWidth, letterSize - 2 * legWidth)

Stop & Help

Convert the loops for DrawS into calls to functions.

What will DrawBar look like in Python?

DrawBar is also yet to be written. A "bar" is just a rectangle of asterisks. This sounds like a nested loop: each iteration of the outer loop prints a single line, and each iteration of the inner loop prints a single asterisk on the line. Here's the code.

def DrawBar(height, width):
lineNum = 0
colNum = 0
for lineNum in range(height):
for colNum in range(width):
print('*', end='')
print('\n')

The functions for DrawIndentedBar and DrawTwoBars are similar. The differences are in the printing of characters on each line; the body of the outer loop must contain additional code to print the blanks for indenting or the blanks between bars. Here is the code for DrawIndentedBar; the extra for statement (boxed) is the main addition.

def DrawIndentedBar(height, width, indentAmt):
BLANK = ' '
lineNum = 0, colNum = 0
for lineNum in range(0, height):
# draw a line of the bar
for colNum in range(0, indentAmt):
print(BLANK, end = " ")
for colNum in range(0, width):
print('*', end= " ")
print('\n')

For ease of understanding, the new inner loop was intentionally written to be similar to the old one, even though Python allows a shorthand way to write sequences of blanks. The constant BLANK is used to avoid any confusion between the blank character and other nonprinting characters.

Stop & Help

Code the DrawTwoBars function in Python.

What does DrawA look like in Python?

Substituting calls to functions for the pseudocode results in the following DrawA function:

def DrawA(letterSize):
topHalfHeight, barHeight, wallHeight, wallWidth, legHeight, legWidth = 0
barHeight = letterSize / 4
topHalfHeight = letterSize / 2
wallHeight = topHalfHeight - barHeight
wallWidth = barHeight
legHeight = letterSize - topHalfHeight - barHeight
legWidth = barHeight
DrawBar(barHeight, letterSize)
DrawTwoBars(wallHeight, wallWidth, letterSize - 2 * wallWidth)
DrawBar(barHeight, letterSize)
DrawTwoBars(legHeight, legWidth, letterSize - 2 * legWidth)

Stop & Help

Code the DrawP function in Python.

Modification

2.10 Redesign the A so that the walls are always longer than the legs.

Analysis

2.11 DrawIndentedBar contains the loop

for colNum in range(0, width):
print('*', end = " ")

Why can’t that loop be rewritten as a single write statement?

Reflection

2.12 What makes letters attractive? What might a programmer do to make the letters even more attractive? Why is it difficult to figure out how to make letters attractive?

Analysis

2.13 A proposal is made to code DrawA as follows, so that the height of the walls is the same as the height of the legs:

wallHeight = letterSize / 4
legHeight = wallHeight
barHeight = (letterSize - wallHeight - legHeight) / 2
wallWidth = barHeight
legWidth = barHeight
DrawBar(barHeight, letterSize)
DrawTwoBars(wallHeight, wallWidth, letterSize - 2 * wallWidth)
DrawBar(barHeight, letterSize)
DrawTwoBars(legHeight, legWidth, letterSize - 2 * legWidth)

Evaluate this proposal.


Development

How should the program be assembled?

The program is now designed and must be developed, that is, turned into a program that runs. Once assembled, it will be a relatively large program: five letter-drawing functions, plus three shape-drawing functions. If it were typed in and tested all at once, and produced incorrect output, it could be difficult to localize the source of the errors. Incremental development—coding and testing the designed program piece by piece, in isolation—is a good development strategy. It allows us to debug the pieces of the program without having bugs from the various pieces interfere with each other.

  1. 11
  2. 12

Chapter 2

Durk Jan de Bruin

How can incremental development be applied to this problem?

Incremental development is often done in a bottom-up fashion, that is, testing and debugging the lowest-level subprograms, then those that call them, and so forth. This is best shown in a call diagram:

The "nodes" in this diagram are the functions in the program, with the more abstract functions—those involving less detail—drawn at the top of the diagram. A line is drawn from one function to another if the first function calls the second.

In the call diagram above, each level corresponds to a level of detail. The second level represents letters. The third level represents shapes. A well organized program has a call diagram in which the levels of abstraction can be easily separated.

It is clear from the call diagram that DrawBar is an important function. All the letter-drawing functions call it. Thus, it makes sense to test DrawBar by itself, to get as much evidence as possible that it works correctly, before adding the routines that call it.

In general, a bottom-up approach to development starts with routines at the bottom of the call diagram—those called by more abstract functions. These routines are tested and debugged in isolation before being combined with routines that depend on them. This is analogous to the process of building a house or a bridge; one wants to know that the foundation is secure before adding the walls, girders, and so on to it.

What extra code should be written to help test the functions?

Extra code must typically be written to test a function like DrawBar in isolation from the rest of the program. This involves a driver program; whose sole purpose is to provide a convenient way to test a function. Such a program in this case might ask the user for a height and a width, and call DrawBar with the input values, as follows:

height, width = 0
print('Type a height and a width:')
height = int(input())
width = int(input())
print('12345678901234567890\n')
DrawBar(height, width)

This program would then be run several times to gather evidence that DrawBar works as intended. Note the output of a line containing column numbers. This makes it even easier for us to see whether the program's output is correct or incorrect. (We included this output for the same reason we included debugging output in Check That Number!)

The amount of code we test and debug for each increment varies depending on our experience with the programming constructs and templates we are using. Since the idea is to make bug-finding easy, however, each increment should be small enough to be able to pin down any errors immediately. For this problem, it makes sense to test and debug the shape drawing functions first, then add each letter-drawing function one by one.

Thus, by starting with the DrawBar, DrawTwoBars, and Drawlndentedbar functions, we can code and test DrawA. (DrawlndentedBar perhaps could be delayed.) Once these are running, we can add DrawP. Once these are running, we can add the routines for the other letters and test the whole program. The Python Code section contains the result.

What tests provide the best evidence that the program works?

In general, test data should be sufficient at least to exercise all statements in the program. One problem with this program is that exercising each statement in the program isn't enough; the statements interact in complicated ways. Referring to Table 2.1, we see that we can get a reasonable picture of the program's correctness only by testing a group of at least four values, one for each possible value of letterSize % 4.

We tested each letter in sizes 5 through 11, the cases in our table. Some of the letters looked weird because the horizontal spacing differed from the vertical spacing on the terminal screen; adjacent characters on a line are closer together than adjacent characters on different lines. We fixed the ratios to get more attractive letters.

Stop & Help

Generate test data for the rest of the letter-drawing functions.

Debugging

2.14 Suppose that, after you type in the program just described, it prints an A as shown on the right. Which function most likely contains the bug? Explain.

Testing

2.15 Describe how to get DrawC running after DrawA without finishing the program.


Outline of Design and Development Questions

These questions summarize the main points of the commentary.

Initial Design Steps

How should one start solving this problem?
What are the big steps for the top-down design of the program?
What decomposition of these steps makes sense?
What quantities need names?
How does the program ask for and read a value for letterSize?
How does the program ask for, read, and draw the letters?
How is the task of reading and drawing the letters decomposed?
What templates do these decompositions use?
What other factors affect the choice of decomposition?
How does the programming environment affect the choice of decomposition?
Which decomposition will be used in this solution?
What progress has been made on the solution so far?
What are ways to decompose the "draw the letter" task?
Which decomposition makes sense for this problem?
How is each letter handled separately?
How does the program decide which letter function to call?
How is the main program coded in Python?

Designing the Letters

What will the letters look like?
How are the dimensions for the letter A determined?
How are the dimensions represented in Python?
How do these dimensions apply to drawing an A?
Must loops be used to draw the letter?
What will DrawBar look like in Python?
What does DrawA look like in Python?

  1. 13
  2. 14

Chapter 2

Durk Jan de Bruin

Development

How should the program be assembled?
How can incremental development be applied to this problem?
What extra code should be written to help test the functions?
What tests provide the best evidence that the program works?


Programmers' Summary

Banners With CLASS presents a single program to print block letters, designed using stepwise refinement. It involves several levels of decomposition:

  • the main program, which reads a letter size, then repeatedly reads a letter and calls the appropriate function to draw it
  • the letter-drawing functions, which combine calls to shape-drawing functions to draw the various letter components
  • the shape-drawing functions, which print asterisks in various rectangular patterns

The solution also involves two choices of decomposition. The first choice is to solve the problem of reading and drawing the letters: should they all be read first, then drawn, or should they be handled letter by letter by reading, then drawing, then reading, then drawing, and so on? We decided to handle the letters one by one, to avoid the clumsiness of managing six variables. (This choice would cause problems, however, in a Python environment that provides only the facility for immediate input, that is, input without waiting for a carriage return.)

The second choice comes when deciding how to decompose the task of drawing letters: should decomposition be in terms of letters, or lines of output, or components of letters? The important concern here is ease of understanding and modification; since the program's purpose is to print letters, it should be organized around functions that deal with letters.

Designing individual letters involves some planning on paper. Sample letters are sketched out, a table made of the dimensions of their components (applying the Multiple Representations principle), and code inferred from the table. The / operator shows up again—Check That Number! uses it also—in computing the dimensions.

Banners With CLASS illustrates the power of the Divide and Conquer principle. The program sounds difficult at first, but by dividing it into pieces and implementing each piece it turns out that the solution is just a sequence of templates that are either familiar or similar to familiar templates.

Three templates likely to be of use in future programming are introduced. One is a variant of "input, process, output," namely "input one, process one" to do item-by-item processing. Another, "select from alternatives," is used to decide which function to call to draw the letter just read. Finally, the template of "do something a specified number of times" is used to design the functions to draw components of each letter.

The Banners With CLASS program is much larger than any of the Check That Number! programs, and, once designed, must be put together carefully. The strategy of incremental development is used to test and debug it. The increments are chosen from the call diagram illustrating how the functions call one another: the bottom-level functions are tested first, then the functions that call them, added one by one.

Driver programs are used early in the development process to provide values for testing the functions. Applying the Persecution Complex principle as in the Check That Number! programs, the test programs used here include debugging output to allow easy detection of errors in the code.

Compared to the Check That Number! 'provide, this program is easier to test since the output is displayed directly on the screen, requiring fewer hand calculations. Design of test data requires somewhat more thought, however. The relative sizes of letter components vary with the overall letter size, and a sufficient set of test data must include examples of all possible patterns.


Making Sense of Banners With CLASS

Debugging

2.16 Suppose a "friend" of yours who often deletes lines from programs used the Banners With CLASS program three times. Each time you got the program back, it had a different bug. Where would you look for these bugs?

  • Every time a letter was input, the program printed block versions of P, Y, T, H, O, N.
  • The block version of the letter T was too short.
  • The letter H had a space in it.

Modification

2.17 Design block versions of the letters T, H ans N. Compare your versions with those produced by the Banners With CLASS program. Explain how yours differ. Change the Banners With CLASS program to produce letters your way.

Application

2.18 Design and test functions to print the block digits 0 through 9.

Application

2.19 Suppose the Banners With CLASS program were going to run on a terminal where the columns were close together but the lines were far apart. What would be necessary to get the program to produce attractive block letters? Implement your idea.

Application

2.20 Write a function to draw a block letter E on a terminal with columns close together and lines far apart.

Analysis

2.21 Suppose a version of Python allowed the user to move the print cursor anywhere on the screen. How would this add to the power of the Banners With CLASS program. What new primitives (functions like DrawBar, Draw LandR, etc.) would be useful.

  1. 15
  2. 16

Chapter 2

Durk Jan de Bruin

Application

2.22 Modify the Banners With CLASS program so that it prints a word of any length. The program should ask the user for the length and then print the word.

Application

2.23 Modify the Banners With CLASS program so that it will print a series of words rather than just one word. The program should keep asking the user for words until the user enters the letter E (for end). Design a banner using words that can be written with the letters P, Y, T, H, O and N and print the banner. An example might be "PYTHON NOT POT."

Reflection

2.24 Compare your design and development decisions to those described in the commentary. What, if anything, would you do differently?

Reflection

2.25 What problems in a program other than bugs might incremental development help detect?

Reflection

2.26 What ideas in the Banners With CLASS solution will you use in designing future programs? What ideas seem wrong or unnecessary for designing programs in the future?


Linking to Previous Case Studies

Reflection

2.27 Compare the design process for Banners With CLASS and Check That Number! What techniques were successful for both?

Analysis

2.28 Compare the use of the "input, process, output" template (programming pattern) in Banners With CLASS and Check That Number! Explain why templates are helpful.

Modification

2.29 Rewrite the character variable version of the Check That Number! program to ask the user how many digits the identification number is supposed to contain, and then to read and process the specified number of digits. (The last digit read will then be the check digit.)


Complete Banners With CLASS Program


# This program asks the user for letters and the size to draw the letters, then prints the letters in block style down the page.

letterSize = 0
letterNum = 0
letter = ''

# Draw a bar of the given height and width.

def DrawBar(height, width):
lineNum = 0
colNum = 0
for lineNum in range(height):
for colNum in range(width):
print('*', end='')
print('\n')

# Draw two bars of the given height and width, separated by the given amount. The thickness of each portion is about 1/4 of the width.

def DrawTwoBars (height, width, separation):
BLANK = ' '
lineNum = 0
colNum = 0

# Draw Two Bars

for lineNum in range(0, height):
for colNum in range(0, width):
print('*', end='')
for colNum in range(0, separation):
print(BLANK, end='')
for colNum in range(0, width):
print('*', end='')
print('\n')

# Draw an indented bar of the given height and width, indented by the given amount. The thickness of the right side is about 1/4 of the width.

def DrawIndentedBar(height, width, indentAmt):
BLANK = ' '
lineNum, colNum = 0
for lineNum in range(0, height):
# draw a line of the bar
for colNum in range(0, indentAmt):
print(BLANK, end='')
for colNum in range(0, width):
print('*', end='')
print('\n')

  1. 17
  2. 18

Chapter 2

Durk Jan de Bruin

# Draw a block P of the given size. The top bar should be roughly 1/4 of the P's height, and the middle bar should be at the top of the bottom half.

def DrawP(letterSize):
topHalfHeight = 0
barHeight =
wallHeight =
legHeight = 0
wallWidth =0
legWidth = 0

# each bar should be around 1/4 of the P

barHeight = letterSize // 4
topHalfHeight = letterSize // 2

# stuff that isn't bar is "walls" of the P

wallHeight = topHalfHeight - barHeight
wallWidth = barHeight
legHeight = letterSize - topHalfHeight - barHeight
legWidth = barHeight
DrawBar(barHeight, letterSize)
DrawTwoBars(wallHeight, wallWidth, letterSize - 2 * wallWidth)
DrawBar(barHeight, letterSize)
DrawBar(legHeight, legWidth)

# Draw a block Y of the given size. The top bar should be roughly 1/4 of the Y's height and the middle bar should be at the top of the bottom half.

def DrawY(letterSize):
topHalfHeight = 0
barHeight = 0
wallHeight = 0
legHeight = 0
wallWidth =0
legWidth = 0

# each bar should be around 1/4 of the P
barHeight = letterSize // 4
topHalfHeight = letterSize // 2
# stuff that isn't bar is "walls" of the P
wallHeight = topHalfHeight - barHeight
wallWidth = barHeight
legHeight = letterSize - topHalfHeight - barHeight
legWidth = barHeight
wallindent = letterSize // 2
DrawTwoBars(topHalfHeight, wallWidth, letterSize - 2 * wallWidth)
DrawBar(barHeight, letterSize)
DrawIndentedBar(legHeight, legWidth, wallindent)

# Draw a block T of the given size. The bars should be roughly 1/4 of the T 's height, with the middle bar roughly at the middle (slightly above if necessary).

def DrawT(letterSize):
barHeight = 0
barHeight = letterSize // 4
wallindent = (letterSize // 2)
DrawBar(barHeight, letterSize)
legHeight = letterSize - barHeight
legWidth = barHeight
DrawIndentedBar(legHeight, legWidth, wallindent)

# Draw a block H of the given size. The top and bottom bars should be roughly 1/4 of the H's height.

def DrawH(letterSize):
topHalfHeight = 0
barHeight = 0
wallHeight = 0
legHeight = 0
wallWidth =0
legWidth = 0
# each bar should be around 1/4 of the P
barHeight = letterSize // 4
topHalfHeight = letterSize // 2
# stuff that isn't bar is "walls" of the P
wallHeight = topHalfHeight - barHeight
wallWidth = barHeight
legHeight = letterSize - topHalfHeight - barHeight
legWidth = barHeight
DrawTwoBars(topHalfHeight, wallWidth, letterSize - 2 * wallWidth)
DrawBar(barHeight, letterSize)
DrawTwoBars(legHeight, legWidth, letterSize - 2 * wallWidth)

# Draw a block O of the given size. The bottom bar should be roughly 1/ 4 of the O's height.

def DrawO(letterSize):
barHeight = 0
middleHeight = 0
wallWidth = 0
barHeight = letterSize // 4
middleHeight = letterSize - barHeight - barHeight
legWidth = barHeight
wallWidth = barHeight
DrawBar(barHeight, letterSize)
DrawTwoBars(middleHeight, legWidth, letterSize - 2 * wallWidth)
DrawBar(barHeight, letterSize)

# Draw a block N of the given size. The top and bottom bars should be roughly 1/4 of the N's height.

def DrawN(letterSize):
topHalfHeight = 0
barHeight = 0
wallHeight = 0
legHeight = 0
wallWidth =0
legWidth = 0
barHeight = 1
topHalfHeight = letterSize // 2
wallHeight = topHalfHeight - barHeight
wallWidth = barHeight
legHeight = letterSize - topHalfHeight - barHeight
legWidth = barHeight
indentamt = 6
DrawBar(barHeight, letterSize)
DrawTwoBars(letterSize, legWidth, letterSize - 2 * wallWidth)
DrawIndentedBar(barHeight, letterSize,indentamt)

# main program

letterSize = int(input('Enter a size for the letters between 5 and 24:'))
print('Type six letters, chosen from PYTHON: ')
for letterNum in range(6):
letter = input()
if letter == 'P' or letter == 'p':
DrawP(letterSize)
if letter == 'Y' or letter == 'y':
DrawY(letterSize)
if letter == 'T' or letter == 't':
DrawT(letterSize)
if letter == 'H' or letter == 'h':
DrawH(letterSize)
if letter == 'O' or letter == 'o':
DrawO(letterSize)
if letter == 'N' or letter == 'n':
DrawN(letterSize)